// Copyright 2013 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.api.ads.dfa.matchtables;

import com.google.api.ads.dfa.axis.factory.DfaServices;
import com.google.api.ads.dfa.lib.client.DfaSession;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;

/**
 * Implementation of LookupObject that will generate Campaign, Ad, and Creative match tables.
 * Uses 2 sub lookup objects, CreativeSubLookup and AdSubLookup, one for ad and associated data
 * lookups, and one for creative and associated data lookups. Also has various utility methods that
 * are used by sub lookup objects.
 *
 * @author Moshe Kahan
 */
class AdCreativeLookup implements LookupObject {

  // MatchTable settings and SOAP remote objects
  private TableSettings tableSettings;

  // Ads
  AdSubLookup adSubLookup;
  // Creatives
  CreativeSubLookup creativeSubLookup;

  /**
   * Constructor.  Besides the DFA service/session objects needed to create SOAP remote objects to
   * the API, constructor also accepts matchTableTypes to determine if both Ad and Creative match
   * tables should be generated.
   *
   * @param dfaServices DFA services object needed to create SOAP remotes to the API
   * @param session DFA session object needed to create SOAP remotes to the API
   * @param tableSettings match table application configuration used as a MatchTable object factory
   */
  AdCreativeLookup(DfaServices dfaServices, DfaSession session, TableSettings tableSettings) {
    this(dfaServices, session, tableSettings,
        new SizeLookupHelper(dfaServices, session),
        new HashMap<Long, Long>(),
        Executors.newFixedThreadPool(TableSettings.API_CLIENT_THREADS, new ThreadFactory() {
          public Thread newThread(Runnable runnable) {
            Thread thread = Executors.defaultThreadFactory().newThread(runnable);
            thread.setDaemon(true);
            return thread;
          }
        }));
  }

  AdCreativeLookup(DfaServices dfaServices, DfaSession session,
      TableSettings tableSettings, SizeLookupHelper sizeLookupHelper,
      Map<Long, Long> creativeToRenderingMap, ExecutorService executor) {
    this(tableSettings,
        new CreativeSubLookup(dfaServices, session, tableSettings, executor,
            sizeLookupHelper, creativeToRenderingMap),
        new AdSubLookup(dfaServices, session, tableSettings, executor,
            sizeLookupHelper, creativeToRenderingMap));
  }

  AdCreativeLookup(TableSettings tableSettings, CreativeSubLookup creativeSubLookup,
      AdSubLookup adSubLookup) {
    this.tableSettings = tableSettings;
    this.creativeSubLookup = creativeSubLookup;
    this.adSubLookup = adSubLookup;
  }

  static <T> ThreadLocal<T> initializeService(final DfaServices dfaServices,
      final DfaSession session, final Class<T> interfaceClass) {
    return new ThreadLocal<T>() {
      protected T initialValue() {
        return dfaServices.get(session, interfaceClass);
      }
    };
  }

  /**
   * Called when LookupObjectManager is parsing the DT file header. Method stores index
   * of ad, advertiser, campaign, and creative columns.
   *
   * @param index in DT file columns
   * @param header name of this DT file field/column
   */
  public void addHeader(int index, String header) {
    if (areCreativeMatchTablesEnabled()) {
      creativeSubLookup.addHeader(index, header);
    }
    if (areAdMatchTablesEnabled()) {
      adSubLookup.addHeader(index, header);
    }
  }

  /**
   * Called to clear the DT header information' usually because you're processing a new file.
   */
  public void clearHeaders() {
    if (areCreativeMatchTablesEnabled()) {
      creativeSubLookup.clearHeaders();
    }
    if (areAdMatchTablesEnabled()) {
      adSubLookup.clearHeaders();
    }
  }

  /**
   * Called after DT file line has been tokenized into array of Strings.  Aggregate over 4 column
   * of interest.  Only aggregate creative id if Creative match table is to be generated. Create 
   * grouping of ads IDs per campaign to make it easier for later SOAP API lookups.
   *
   * @param values array of tokenized Strings of DT file line
   */
  public void addValues(String[] values) {
    if (areCreativeMatchTablesEnabled()) {
      creativeSubLookup.addValues(values);
    }
    if (areAdMatchTablesEnabled()) {
      adSubLookup.addValues(values);
    }
  }

  /**
   * Called when parsing/aggregation is complete. Will lookup via SOAP all unique DFA entries
   * in the DT file and pass data to MatchTable class.
   *
   * @param outputPath location of match table to be generated.
   */
  public void generateMatchTable(String outputPath)
      throws MatchTableFileException, DfaRemoteException {
    // Do creative lookups if creative match tables are active or if 
    // creative_ad_assignment match table is active as we need to retrieve 
    // creatives for the creative ID to rendering ID map.
    if (areCreativeMatchTablesEnabled()) {
      creativeSubLookup.generateMatchTable(outputPath);
    }

    if (areAdMatchTablesEnabled()) {
      adSubLookup.generateMatchTable(outputPath);
    }
  }

  // Utility methods

  boolean areCreativeMatchTablesEnabled() {
    return (tableSettings.hasMatchTable(MatchTable.Type.CREATIVE_AD_ASSIGNMENTS) ||
        tableSettings.hasMatchTable(MatchTable.Type.CREATIVE) ||
        tableSettings.hasMatchTable(MatchTable.Type.CUSTOM_CREATIVE_FIELDS) ||
        tableSettings.hasMatchTable(MatchTable.Type.MET_EVENT));
  }

  boolean areAdMatchTablesEnabled() {
    return (tableSettings.hasMatchTable(MatchTable.Type.AD) ||
        tableSettings.hasMatchTable(MatchTable.Type.AD_PAGE_ASSIGNMENTS) ||
        tableSettings.hasMatchTable(MatchTable.Type.CREATIVE_AD_ASSIGNMENTS) ||
        tableSettings.hasMatchTable(MatchTable.Type.KEYVAL));
  }

  // Utility method to try to use factory to create match table, only add to List if not null.
  static MatchTable createMatchTable(TableSettings tableSettings, MatchTable.Type type,
      List<MatchTable> matchTableList) {
    MatchTable table = tableSettings.createMatchTable(type);
    if (table != null) {
      matchTableList.add(table);
    }
    return table;
  }

  static void waitForFutures(List<Future<?>> futures) throws DfaRemoteException{
    for (Future<?> future : futures) {
      try {
        future.get();
      } catch (Exception e) {
        if (e.getCause() instanceof DfaRemoteException) {
          throw (DfaRemoteException) e.getCause();
        }
        throw new DfaRemoteException("Error in contacting DFA Remote API", e);
      }
    }
  }

  static boolean isValid(String value) {
    return (value != null && !value.isEmpty() && !value.equals("0"));
  }

  // Utility method to try to add to Strings Set entry in map; creates Set if doesn't exist.
  static <T1, T2> void addToSetToMap(Map<T1, Set<T2>> mapOfStringToSet, T1 key, T2 value) {
    Set<T2> setOfStrings = mapOfStringToSet.get(key);
    if (setOfStrings == null) {
      setOfStrings = new HashSet<T2>();
      mapOfStringToSet.put(key, setOfStrings);
    }
    setOfStrings.add(value);
  }
}
